#include <iostream>
#include <stdio.h>
#include <fstream>
#include <cstring>
#include "document.h"


#define INVALID_POSITION Position {-1,-1}


using namespace std;

Document::Document(){
    this->_dirty = false;

}


Document::~Document(){
    delete this->FileName;
    for(int i=0;i<this->_rows.size();i++){
        Row* p = this->_rows[i];
        delete p;
    }
}


int Document::Open(char* fileName){
    fstream fs(fileName);
    FileType fileType = FileType::Create(fileName);
    if (!fs.good()){
        return -1;
    }
    string line;
    Row* p_row;
    while(getline(fs,line)){
        p_row = new Row(line);
        this->_rows.push_back(p_row);
    }
    this->FileName = new string();
    *(this->FileName) = fileName;
    this->_fileType = fileType;
    return 0;
}




Row* Document::GetRowByIndex(int index){
    if (IsEmpty() || (_rows.size()-1) < index){
        return NULL;
    }
    Row* row = _rows[index];
    return row;
}



bool Document::IsEmpty(){
    return _rows.size() == 0;
}


int Document::Len(){
    return _rows.size();
}


void Document::Insert(Position pos,char c){
    if (pos.x > this->Len()){
        return;
    }
    this->_dirty = true;

    if (c=='\n'){
        this->InsertNewLine(pos);
    } else if (pos.x == this->Len()){
        string str;
        str.push_back(c);
        Row* newRow = new Row(str);
        _rows.push_back(newRow);
    } else {
        Row* row  = _rows[pos.x];
        row->Insert(pos.y,c);
    }
    this->UnhighlightRows(pos.x);
}


void Document::Delete(Position at){
    int len = this->Len();
    if (at.x >= len){
        return;
    }
    
    this->_dirty = true;
    if (at.y == _rows[at.x]->Length() && at.x < len -1){
        Row* nextRow = _rows[at.x+1];
        _rows.erase(_rows.begin()+at.x+1);
        Row* row = _rows[at.x];
        row->Append(nextRow);
    } else {
        Row* row = _rows[at.x];
        row->Delete(at.y);
    }
    this->UnhighlightRows(at.x);
}


void Document::InsertNewLine(Position at){
    if (at.x == this->Len()){
        _rows.push_back(new Row(""));
        return;
    }

    Row* newRow = this->_rows[at.x]->Split(at.y);
    this->_rows.insert(this->_rows.begin()+at.x+1,newRow);
}

int Document::Save(){
    if (this->FileName !=NULL){
        ofstream os(this->FileName->c_str(),std::ofstream::binary);
        this->_fileType = FileType::Create(*(this->FileName));
        for(int i=0;i<this->Len();i++){
           int contentLen = this->_rows[i]->Length();
           char* lineBuffer = new char[contentLen+1];
           strcpy(lineBuffer,this->_rows[i]->GetContent());
           lineBuffer[contentLen] = '\n';
           os.write(lineBuffer,contentLen+1);
           delete[] lineBuffer;
        }
        os.close();
        this->_dirty = false;
        return 0;
    } else {
        return -1;
    }
    
}


bool Document::IsDirty(){
    return this->_dirty;
}


Position Document::Find(string str,Position at,SearchDirection direction){
    if (at.x >= this->_rows.size()){
        return INVALID_POSITION;
    }

    Position pos = Position{at.x,at.y};
    int start = 0;
    int end = Util::SaturatingAdd(at.x,1);
    if (direction == SearchDirection::Forward){
        start = pos.x;
        end = this->_rows.size();
    }

    for(int i=start;i<end;i++){
        Row* p_row = this->_rows[pos.x];
        if (p_row !=NULL){
            int index = p_row->Find(str,pos.y,direction);
            if (index>=0){
                pos.y = index;
                return pos;
            }
            if (direction == SearchDirection::Forward){
                pos.x = Util::SaturatingAdd(pos.x,1);
                pos.y = 0;
            } else {
                pos.x = Util::SaturatingSub(pos.x,1);
                pos.y = this->_rows[pos.x]->Length();
            }
        }else {
            return INVALID_POSITION;
        }
    }
    return INVALID_POSITION;
}

void Document::Highlight(string word){
    this->Highlight(word,-1);
}

void Document::Highlight(string word,int until){
    bool start_with_comment = false;
    int until_index = this->_rows.size();
    if (until >= 0 && Util::SaturatingAdd(until,1)<this->_rows.size()){
        until_index = Util::SaturatingAdd(until,1);
    }

    for(int i=0;i<until_index;i++){
        start_with_comment = this->_rows[i]->Highlight(this->_fileType.HlOptions(),word,start_with_comment);
    }
}

void Document::UnhighlightRows(int start){
    int unhl_start =Util::SaturatingSub(start,1);
    for(int i = unhl_start;i< this->_rows.size();i++){
        this->_rows.at(i)->IsHighlighted = false;
    }
}

string Document::GetFileType(){
    return this->_fileType.Name();
    
}
